<?php
/*
 * Esto puede utilizarse para representar cualquier tag html con runat="server"
 	Hay subclases específicas para elementos habituales, como InputControl (para eltos de form) e ImageControl
 	
 	Esta moña se podría optimizar si me guardase el html crudo inicial, para imprimirlo a no ser que el usuario no haga cambios
 	En este caso, se retrasaría todo el parseo hasta que se hiciese un cambio o un Get de alguna property especial
 	
 	Otra opción es que exista un método para poner los valores iniciales a pelo y rápidamente, sin andar haciendo setEsto o setAquello
 	De esta manera, el compilador determinaría la composición inicial, y se inicializaría el objeto más rápidamente
 */
 
 class HtmlControl extends Control
 {
 	protected $clientId;
 	
 	private $attrs;     		//atributos html (salvo style y class)
 	private $style;			//en concreto, para el atributo style
 	private $cssClass;			//en concreto, para el atributo class
 	private $Visible = true;
 	
 	
 	private $tag;				//pos eso
 	private $shortTag;			//bool
 	private $tagModified;		//flag para detectar cuándo el tag ha sido invalidado
 	private $contentModified;	//flag para detectar cuándo el innerHTML ha sido invalidado
 		
 	// ay, ay ay - si el usuario toca esto, hay que eliminar todos los subcontrols?
 	// del mismo modo, si se tocan los subcontrols, se va a tomar por saco el innerHTML?
 	//private $innerHTML;
 	//el innerHTML pasa a ser una lista de trozos de código fuente o bien llamada al render de un hijo
 	private $innerBlocks;
 	private $tagHTML;

/* MAGIC ZONE
	- Implemento properties de esta manera chunga
	- Me interesan sobre todo Visible y InnerHtml
	- De todos modos, lo implemento tb con funciones Get y SEt
	*/
	
	public function __get($name)
	{
		switch($name)
		{
			case "Visible": return $this->Visible;
			case "InnerHtml": return $this->Render();
			default: throw new Exception("Invalid property");
		}		
	}
	//aquí aprovecharé para poner la conversión desde string?
	//no, mejor haré una batería de métodos separada para procesar el establecimiento de propiedades especiales desde tags
	public function __set($name, $value)
	{
		switch($name)
		{
			case "Visible": $this->Visible = ($value?true:false); break;
			case "InnerHtml":
				$this->innerBlocks = array(array("is_control"=>false, "code"=>$value));
				// ¿no debería marcar el tagModified, o alguna mierda de esas?				
				break;
			default: throw new Exception("Invalid property");
		}		
	}

	public function Render()
 	{
		$dbg = ($this->id=="hFechaHoy");
	
 		$ret = "";
 		if($this->Visible)
 		{
			if(!$this->tagModified)
			{
				if($dbg) echo "\n<<<<<<<<<<<<<<<< ".$this->id." UNMODIFIED >>>>>>>>>>>>>>>\n";
				$ret.=$this->tagHTML;
			}
			else
			{
				if($dbg) echo "\n>>>>>>>>>>>>>>>\ ".$this->id." MODIFIED <<<<<<<<<<<<<<<<\n";
				//construir el tag a partir de los atributos
				$ret.="<".$this->tag.$this->PrintAttrs();
				$class = trim($this->PrintCssClass());
				$style = $this->PrintStyle();
				if($class)
					$ret.=" class=\"$class\"";
				if($style)
					$ret.=" style=\"$style\"";
				
				if($this->shortTag)
					$ret.=" />";
				else
					$ret.=">";
			}
			
			if(!$this->shortTag)
			{
				if($dbg) echo "\n>>>>>>>>>>>>>>>\ ".$this->id." TAG LARGO <<<<<<<<<<<<<<<<\n";
				foreach($this->innerBlocks as $block)
				{		
					if($dbg) echo "\n>>>>>>>>>>>>>>>\ ".$this->id." BLOQUE (long. contenido=".strlen($block["code"]).") <<<<<<<<<<<<<<<<\n";
					if(!$block["is_control"]) $ret.=$block["code"];
					else 
					{						
						$ret.= $this->container->getControl($block["code"])->Render();
					}
				}			
				$ret.="</".$this->tag.">";
			}
		}
		return $ret;
 	}
 	
 	// Construye el html interior. Como esto es conflictivo, lo pongo aquí aparte
 	private function innerRender()
 	{
 	}
 	
 	function __construct($arg, $container)
 	{ 		
 		$this->attrs = array();
 		$this->style = array();
 		$this->cssClass = array();
 		$this->tagModified = false;
		$this->contentModified = false;
		$this->innerBlocks = array();
		
		if(is_string($arg))
			$this->buildFromSource($arg, $container);
		else
			$this->buildFromObject($arg, $container);
 	}
 	
 	function buildFromSource($html)
 	{ 		
 		//Aquí hago un parse del patín, y acabo estableciendo $tag, $style, $cssClass y $attrs
 		//Quiero creer que nunca se va a dar el caso, y que esto será más bien en containers (o nunca)
 	}
 	function buildFromObject($obj, $container)
 	{
 		global $debug_level;
 		//esto sería para que el compilador se currase el parseo
		/* 
		$obj es un array asociativo con las posiciones:
		 - attrs:			parejas clave-valor con atributos html y valores
		 - tag			el tag en sí (div, img, whatever)
		 - short_tag		booleano
		 - id			tal y como estaba en origen (respecto al contenedor)
		 - tag_literal		el tag de apertura completo
		 - inner_content	el innerHTML a bloques		 
		 - short_tag:		ids de los subparsers (subcontroles)
		*/
		
		$this->tag = $obj["tag"];
		$this->id = $obj["id"];
		$this->innerBlocks = $obj["inner_content"];
		$this->tagHTML = $obj["tag_literal"];
		$this->container = $container;
		$this->short_tag = $obj["short_tag"];
		
		$this->controlIds = $obj["subcontrol_ids"];
		
		
		foreach($obj["attrs"] as $key=>$val)
		{
			if(!in_array($key, Control::$restricted_attrs))
				$this->SetAttr($key, $val);
		}
		$this->tagModified = false;
		$this->contentModified = false;
 	}


 	
 	
/* ------------------------------------------------------------------------------------------------------------------------
 * FUNCIONES DE ATRIBUTOS HTML
 */
 	public function GetAttr($src_name)
 	{ 		
 		$name = strtolower($src_name);
 		
 		if($name=="style")
 			return $this->PrintStyle();
 		else if($name=="class")
 			return $this->PrintCssClass();
 		else
 			return $this->attrs[$name];
 	}
 	public function HasAttr($src_name)
 	{
 		$name = strtolower($src_name);
 		
 		if($name=="style")
 			return sizeof($this->style)>0;
		if($name=="class")
 			return sizeof($this->cssClass)>0;
		else
 			return array_key_exists($name, $this->attrs);
 	}
 	public function SetAttr($src_name, $value)
 	{ 	
 		$name = strtolower($src_name);
 			
 		if($name=="style")
 			$this->SetRawStyle($value);
		else if($name=="class")
			$this->SetRawCssClass($value);
		else
			$this->attrs[$name] = $value;
			
		//optimizable haciendo que sólo pase cuando cambia efectivamente el valor
		$this->tagModified = true;
 	} 	
 	public function RemoveAttr($src_name)
 	{
 		$name = strtolower($src_name);
 		
 		if($name=="style")
 			$this->ClearStyle();
		else if($name=="class")
			$this->ClearCssClass();
		else
 			unset($this->attrs[$name]);
 			
		//optimizable haciendo que sólo pase cuando se quita efectivamente el valor (podría no estar)
		$this->tagModified = true;
 	}
 	public function PrintAttrs()
 	{
 		$ret = "";
 		$prim = true;
 		foreach($this->attrs as $attr=>$val)
 		{
 			if($prim) $prim = false;
 			else $ret.=" ";
 			$ret.="$attr=\"$val\""; 			
 		}
 		return $ret;
 	}
 	
 	
 	
 	
/* ------------------------------------------------------------------------------------------------------------------------
 * FUNCIONES DE ESTILO CSS
 */
 	public function GetStyle($src_style)
 	{
 		$style = strtolower($src_style); 		
 		return $this->style[$style];
 	}
 	//esta es para parsear el texto completo del tag
 	//Esto es mejorable con regex para test previo, y split
 	public function SetRawStyle($htm) 	
 	{
 		$pares = explode(";", $htm);
 		$this->ClearStyle();
 		for($i=0; $i<sizeof($pares); $i++)
 		{
 			$tmp = explode(":", trim($pares[$i]));
 			$this->SetStyle(trim($tmp[0]), trim($tmp[1]));
 		}
 		
		$this->tagModified = true;
 	}
 	public function SetStyle($src_style, $value)
 	{
 		$style = strtolower($src_style); 		
 		$this->style[$style] = $value;
 		
 		$this->tagModified = true;
 	}
 	public function HasStyle($src_style)
 	{
 		$style = strtolower($src_style);
 		return array_key_exists($style);
 	} 	
 	public function RemoveStyle($src_style)
 	{
 		$style = strtolower($src_style);
 		unset($this->style[$style]);
 		
 		$this->tagModified = true;
 	}
 	public function PrintStyle()
 	{
 		$ret = "";
 		foreach($this->style as $style=>$value)
 		{
 			$ret.=$style.":".$value."; ";
 		}
 		return $ret;
 	}
 	public function ClearStyle()
 	{
 		$this->style = array();
 		
 		$this->tagModified = true;
 	}
 	
 	
 	
/* ------------------------------------------------------------------------------------------------------------------------
 * FUNCIONES DE CLASES CSS
 */
	public function PrintCssClass()
	{
		return implode(" ",$this->cssClass);
	}
	public function HasCssClass($class)
	{
		return in_array($class, $this->cssClass);
	}
	public function AddCssClass($class)
	{
		if(!$this->HasCssClass($class))
			$this->cssClass[] = $class;
			
		$this->tagModified = true;
	}
	public function RemoveCssClass($class)
	{
		if($this->HasCssClass($class))
		{
			$ix = array_search($class, $this->cssClass);
			unset($this->cssClass[$ix]);
			//re-indexar:
			$this->cssClass = array_values($this->cssClass);
			
			$this->tagModified = true;
		}		
	}
	public function SetRawCssClass($htm)
	{
		$tmp = explode(" ",$htm);
		foreach($tmp as $class)
		{
			$this->AddCssClass(trim($class));
		}
		
		$this->tagModified = true;
	}
	public function ClearCssClass()
	{
		$this->cssClass = array();
		
		$this->tagModified = true;
	}
 	 	
 	 	
 	 	
/* ------------------------------------------------------------------------------------------------------------------------
 * FUNCIONES DE UTILIDAD
 */ 	 	
	public function Show()
	{
		$this->SetStyle("display", "");
		$this->SetStyle("visibility", "visible");
	}
	public function Hide()
	{
		$this->SetStyle("display", "none");
		$this->SetStyle("visibility", "hidden");
	}
	
 }
 
?>
